#include <windows.h>
#include <cstdlib>
#include <vector>

#include "Simulation.h"
#include "GraphicsRenderer.h"
#include "Model.h"
#include "ShaderData.h"
#include "ClassicSimulationInput.h"
#include "Snapshot.h"

#define IDI_PATHSIMULATION 107
#define NUM_RANDOM_CUBES (0)
#define RANDOM_CUBE_SIZE (0.1f)
#define RANDOM_CUBE_RANGE_LOWER (DirectX::XMFLOAT3(-30.f, 0.f, -30.f))
#define RANDOM_CUBE_RANGE_UPPER (DirectX::XMFLOAT3(30.f, 20.f, 30.f))

HRESULT InitWindow( HINSTANCE hInstance, int nCmdShow, HWND& windowHandle);

int WINAPI wWinMain( _In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow )
{
	UNREFERENCED_PARAMETER( hPrevInstance );
	UNREFERENCED_PARAMETER( lpCmdLine );
	HWND windowHandle = nullptr;

	if( FAILED( InitWindow( hInstance, nCmdShow, windowHandle) ) )
	{
		return 0;
	}
	GraphicsRenderer graphicsRenderer(windowHandle, DirectX::XMFLOAT3(5.f, 18.f, 5.f), DirectX::XMFLOAT3(-6.f, 16.f, -3.f));

#pragma region Model initialization
	Texture seafloorTexture(L"seafloor.dds");
	Texture seafloorNormalMap(L"seafloor_normal.dds");
	SimpleVertex vertices[] =
	{
		{ DirectX::XMFLOAT3( -1.0f, 1.0f, -1.0f ), DirectX::XMFLOAT2( 0.f, 0.f ), DirectX::XMFLOAT3( 0.0f, 1.0f, 0.0f ), DirectX::XMFLOAT3(1,0,0), DirectX::XMFLOAT3(0, 0, 1), DirectX::XMFLOAT3(0.f, 0.f, 0.f)},
		{ DirectX::XMFLOAT3( 1.0f, 1.0f, -1.0f ), DirectX::XMFLOAT2( 0.f, 1.f ), DirectX::XMFLOAT3( 0.0f, 1.0f, 0.0f ), DirectX::XMFLOAT3(1,0,0), DirectX::XMFLOAT3(0, 0, 1), DirectX::XMFLOAT3(0.f, 0.f, 0.f)},
		{ DirectX::XMFLOAT3( 1.0f, 1.0f, 1.0f ), DirectX::XMFLOAT2( 1.f, 1.f ), DirectX::XMFLOAT3( 0.0f, 1.0f, 0.0f ), DirectX::XMFLOAT3(1,0,0), DirectX::XMFLOAT3(0, 0, 1), DirectX::XMFLOAT3(0, 0, 0)},
		{ DirectX::XMFLOAT3( -1.0f, 1.0f, 1.0f ), DirectX::XMFLOAT2( 1.f, 0.f ), DirectX::XMFLOAT3( 0.0f, 1.0f, 0.0f ), DirectX::XMFLOAT3(1,0,0), DirectX::XMFLOAT3(0, 0, 1), DirectX::XMFLOAT3(0.f, 0.f, 0.f)},

		{ DirectX::XMFLOAT3( -1.0f, -1.0f, -1.0f ), DirectX::XMFLOAT2( 0.0f, 0.0f ), DirectX::XMFLOAT3( 0.0f, -1.0f, 0.0f ), DirectX::XMFLOAT3(1,0,0), DirectX::XMFLOAT3(0, 0, 1), DirectX::XMFLOAT3(0, 0, 0)},
		{ DirectX::XMFLOAT3( 1.0f, -1.0f, -1.0f ), DirectX::XMFLOAT2( 1.0f, 0.0f ), DirectX::XMFLOAT3( 0.0f, -1.0f, 0.0f ), DirectX::XMFLOAT3(1,0,0), DirectX::XMFLOAT3(0, 0, 1), DirectX::XMFLOAT3(0, 0, 0)},
		{ DirectX::XMFLOAT3( 1.0f, -1.0f, 1.0f ), DirectX::XMFLOAT2( 1.0f, 1.0f ), DirectX::XMFLOAT3( 0.0f, -1.0f, 0.0f ), DirectX::XMFLOAT3(1,0,0), DirectX::XMFLOAT3(0, 0, 1), DirectX::XMFLOAT3(0, 0, 0)},
		{ DirectX::XMFLOAT3( -1.0f, -1.0f, 1.0f ), DirectX::XMFLOAT2( 0.0f, 1.0f ), DirectX::XMFLOAT3( 0.0f, -1.0f, 0.0f ), DirectX::XMFLOAT3(1,0,0), DirectX::XMFLOAT3(0, 0, 1), DirectX::XMFLOAT3(0, 0, 0)},

		{ DirectX::XMFLOAT3( -1.0f, -1.0f, 1.0f ), DirectX::XMFLOAT2( 0.0f, 1.0f ), DirectX::XMFLOAT3( -1.0f, 0.0f, 0.0f ), DirectX::XMFLOAT3(0, 1, 0), DirectX::XMFLOAT3(0, 0, 1), DirectX::XMFLOAT3(0, 0, 0)},
		{ DirectX::XMFLOAT3( -1.0f, -1.0f, -1.0f ), DirectX::XMFLOAT2( 1.0f, 1.0f ), DirectX::XMFLOAT3( -1.0f, 0.0f, 0.0f ), DirectX::XMFLOAT3(0, 1, 0), DirectX::XMFLOAT3(0, 0, 1), DirectX::XMFLOAT3(0, 0, 0)},
		{ DirectX::XMFLOAT3( -1.0f, 1.0f, -1.0f ), DirectX::XMFLOAT2( 1.0f, 0.0f ), DirectX::XMFLOAT3( -1.0f, 0.0f, 0.0f ), DirectX::XMFLOAT3(0, 1, 0), DirectX::XMFLOAT3(0, 0, 1), DirectX::XMFLOAT3(0, 0, 0)},
		{ DirectX::XMFLOAT3( -1.0f, 1.0f, 1.0f ), DirectX::XMFLOAT2( 0.0f, 0.0f ), DirectX::XMFLOAT3( -1.0f, 0.0f, 0.0f ), DirectX::XMFLOAT3(0, 1, 0), DirectX::XMFLOAT3(0, 0, 1), DirectX::XMFLOAT3(0, 0, 0)},

		{ DirectX::XMFLOAT3( 1.0f, -1.0f, 1.0f ), DirectX::XMFLOAT2( 1.0f, 1.0f ), DirectX::XMFLOAT3( 1.0f, 0.0f, 0.0f ), DirectX::XMFLOAT3(0, 1, 0), DirectX::XMFLOAT3(0, 0, 1), DirectX::XMFLOAT3(0, 0, 0)},
		{ DirectX::XMFLOAT3( 1.0f, -1.0f, -1.0f ), DirectX::XMFLOAT2( 0.0f, 1.0f ), DirectX::XMFLOAT3( 1.0f, 0.0f, 0.0f ), DirectX::XMFLOAT3(0, 1, 0), DirectX::XMFLOAT3(0, 0, 1), DirectX::XMFLOAT3(0, 0, 0)},
		{ DirectX::XMFLOAT3( 1.0f, 1.0f, -1.0f ), DirectX::XMFLOAT2( 0.0f, 0.0f ), DirectX::XMFLOAT3( 1.0f, 0.0f, 0.0f ), DirectX::XMFLOAT3(0, 1, 0), DirectX::XMFLOAT3(0, 0, 1), DirectX::XMFLOAT3(0, 0, 0)},
		{ DirectX::XMFLOAT3( 1.0f, 1.0f, 1.0f ), DirectX::XMFLOAT2( 1.0f, 0.0f ), DirectX::XMFLOAT3( 1.0f, 0.0f, 0.0f ), DirectX::XMFLOAT3(0, 1, 0), DirectX::XMFLOAT3(0, 0, 1), DirectX::XMFLOAT3(0, 0, 0)},

		{ DirectX::XMFLOAT3( -1.0f, -1.0f, -1.0f ), DirectX::XMFLOAT2( 0.0f, 1.0f ), DirectX::XMFLOAT3( 0.0f, 0.0f, -1.0f ), DirectX::XMFLOAT3(0, 1, 0), DirectX::XMFLOAT3(1, 0, 0), DirectX::XMFLOAT3(0, 0, 0)},
		{ DirectX::XMFLOAT3( 1.0f, -1.0f, -1.0f ), DirectX::XMFLOAT2( 1.0f, 1.0f ), DirectX::XMFLOAT3( 0.0f, 0.0f, -1.0f ), DirectX::XMFLOAT3(0, 1, 0), DirectX::XMFLOAT3(1, 0, 0), DirectX::XMFLOAT3(0, 0, 0)},
		{ DirectX::XMFLOAT3( 1.0f, 1.0f, -1.0f ), DirectX::XMFLOAT2( 1.0f, 0.0f ), DirectX::XMFLOAT3( 0.0f, 0.0f, -1.0f ), DirectX::XMFLOAT3(0, 1, 0), DirectX::XMFLOAT3(1, 0, 0), DirectX::XMFLOAT3(0, 0, 0)},
		{ DirectX::XMFLOAT3( -1.0f, 1.0f, -1.0f ), DirectX::XMFLOAT2( 0.0f, 0.0f ), DirectX::XMFLOAT3( 0.0f, 0.0f, -1.0f ), DirectX::XMFLOAT3(0, 1, 0), DirectX::XMFLOAT3(1, 0, 0), DirectX::XMFLOAT3(0, 0, 0)},

		{ DirectX::XMFLOAT3( -1.0f, -1.0f, 1.0f ), DirectX::XMFLOAT2( 1.0f, 1.0f ), DirectX::XMFLOAT3( 0.0f, 0.0f, 1.0f ), DirectX::XMFLOAT3(0, 1, 0), DirectX::XMFLOAT3(1, 0, 0), DirectX::XMFLOAT3(0, 0, 0)},
		{ DirectX::XMFLOAT3( 1.0f, -1.0f, 1.0f ), DirectX::XMFLOAT2( 0.0f, 1.0f ), DirectX::XMFLOAT3( 0.0f, 0.0f, 1.0f ), DirectX::XMFLOAT3(0, 1, 0), DirectX::XMFLOAT3(1, 0, 0), DirectX::XMFLOAT3(0, 0, 0)},
		{ DirectX::XMFLOAT3( 1.0f, 1.0f, 1.0f ), DirectX::XMFLOAT2( 0.0f, 0.0f ), DirectX::XMFLOAT3( 0.0f, 0.0f, 1.0f ), DirectX::XMFLOAT3(0, 1, 0), DirectX::XMFLOAT3(1, 0, 0), DirectX::XMFLOAT3(0, 0, 0)},
		{ DirectX::XMFLOAT3( -1.0f, 1.0f, 1.0f ), DirectX::XMFLOAT2( 1.0f, 0.0f ), DirectX::XMFLOAT3( 0.0f, 0.0f, 1.0f ), DirectX::XMFLOAT3(0, 1, 0), DirectX::XMFLOAT3(1, 0, 0), DirectX::XMFLOAT3(0, 0, 0)},
	};
	WORD indices[] =
	{
		3,1,0,
		2,1,3,

		6,4,5,
		7,4,6,

		11,9,8,
		10,9,11,

		14,12,13,
		15,12,14,

		19,17,16,
		18,17,19,

		22,20,21,
		23,20,22
	};

	ModelRawData modelData;
	modelData.Color = DirectX::XMFLOAT4(0.9f, 0.5f, 0.2f, 1.0f);
	modelData.Position = DirectX::XMFLOAT3(30.0f, 1.5f, 2.5f);
	modelData.Rotation = DirectX::XMFLOAT3(0.3f, 0.8f, 0.7f);
	modelData.numVertices = 24;
	modelData.vertices = vertices;
	modelData.numIndices = 36;
	modelData.indices = indices;

	Model model1(modelData, seafloorTexture, seafloorNormalMap);
	graphicsRenderer.AddModel(model1);

	modelData.Position = DirectX::XMFLOAT3(2.0f, 5.5f, 3.5f);
	modelData.Rotation = DirectX::XMFLOAT3(0.4f, 0.f, 0.6f);
	modelData.Color = DirectX::XMFLOAT4(0.4f, 0.4f, 1.0f, 1.0f);
	Model model2(modelData, seafloorTexture, seafloorNormalMap);
	graphicsRenderer.AddModel(model2);

	WORD indices2[] =
	{
		3,1,0,
		2,1,3,
	};

	SimpleVertex vertices2[] =
	{
		{ DirectX::XMFLOAT3( -100.0f, 0.0f, -100.0f ),
		DirectX::XMFLOAT2( 0.0f, 0.0f ),
		DirectX::XMFLOAT3( 0.f, 1.f, 0.f),
		DirectX::XMFLOAT3(1.f, 0.f, 0.f),
		DirectX::XMFLOAT3(0.f, 0.f, 1.f),
		DirectX::XMFLOAT3(0, 0, 0)},
		{ DirectX::XMFLOAT3( 100.0f, 0.0f, -100.0f ),
		DirectX::XMFLOAT2( 100.0f, 0.0f ),
		DirectX::XMFLOAT3( 0.f, 1.f, 0.f),
		DirectX::XMFLOAT3(1.f, 0.f, 0.f),
		DirectX::XMFLOAT3(0.f, 0.f, 1.f),
		DirectX::XMFLOAT3(0, 0, 0)},
		{ DirectX::XMFLOAT3( 100.0f, 0.0f, 100.0f ),
		DirectX::XMFLOAT2( 100.0f, 100.0f ),
		DirectX::XMFLOAT3( 0.f, 1.f, 0.f),
		DirectX::XMFLOAT3(1.f, 0.f, 0.f),
		DirectX::XMFLOAT3(0.f, 0.f, 1.f),
		DirectX::XMFLOAT3(0, 0, 0)},
		{ DirectX::XMFLOAT3( -100.0f, 0.0f, 100.0f ),
		DirectX::XMFLOAT2( 0.0f, 100.0f ),
		DirectX::XMFLOAT3( 0.f, 1.f, 0.f),
		DirectX::XMFLOAT3(1.f, 0.f, 0.f),
		DirectX::XMFLOAT3(0.f, 0.f, 1.f),
		DirectX::XMFLOAT3(0, 0, 0)},
	};
	modelData.vertices = vertices2;
	modelData.indices = indices2;
	modelData.numVertices = 4;
	modelData.numIndices = 6;
	modelData.Position = DirectX::XMFLOAT3(0.f, 0.f, 0.f);
	modelData.Rotation = DirectX::XMFLOAT3(0.f, 0.f, 0.f);
	modelData.Color = DirectX::XMFLOAT4(0.75f, 0.75, 0.75, 1.f);

	Model model3(modelData, seafloorTexture, seafloorNormalMap);
	graphicsRenderer.AddModel(model3);

	modelData.numVertices = 24;
	modelData.vertices = vertices;
	modelData.numIndices = 36;
	modelData.indices = indices;
	modelData.Position = DirectX::XMFLOAT3(2.f, 10.f, -5.f);
	modelData.Color = DirectX::XMFLOAT4(0.5f, 0.2f, 0.2f, 1.f);
	Model model4(modelData, seafloorTexture, seafloorNormalMap);
	graphicsRenderer.AddModel(model4);

	modelData.Color = DirectX::XMFLOAT4(0.2f, 0.9f, 0.2f, 1.f);
	modelData.Position = DirectX::XMFLOAT3(4.f, 8.f, 3.f);
	modelData.Rotation = DirectX::XMFLOAT3(-0.5f, 0.2f, 0.8f);
	Model model5(modelData, seafloorTexture, seafloorNormalMap);
	graphicsRenderer.AddModel(model5);

#if NUM_RANDOM_CUBES > 0
	Model randomModels[NUM_RANDOM_CUBES];
	for(int i = 0; i < NUM_RANDOM_CUBES; ++i)
	{
		const DirectX::XMFLOAT3 lowerBound = RANDOM_CUBE_RANGE_LOWER;
		const DirectX::XMFLOAT3 upperBound = RANDOM_CUBE_RANGE_UPPER;
		modelData.Position = DirectX::XMFLOAT3(rand() / static_cast<float>(RAND_MAX) * (upperBound.x - lowerBound.x) + lowerBound.x,
			rand() / static_cast<float>(RAND_MAX) * (upperBound.y - lowerBound.y) + lowerBound.y,
			rand() / static_cast<float>(RAND_MAX) * (upperBound.z - lowerBound.z) + lowerBound.z);
		modelData.Color = DirectX::XMFLOAT4(rand() / static_cast<float>(RAND_MAX), rand() / static_cast<float>(RAND_MAX), rand() / static_cast<float>(RAND_MAX), 1.0f);
		modelData.Rotation = DirectX::XMFLOAT3(rand() / static_cast<float>(RAND_MAX), rand() / static_cast<float>(RAND_MAX), rand() / static_cast<float>(RAND_MAX));
		randomModels[i] = Model(modelData, seafloorTexture, seafloorNormalMap);
		graphicsRenderer.AddModel(randomModels[i]);
	}
#endif

#pragma endregion Model initialization
	ClassicSimulationInput input(windowHandle);//never call twice
	Snapshot snapshot;
	DirectX::XMStoreFloat4(&snapshot.rotation, DirectX::XMQuaternionIdentity());
	snapshot.translation = DirectX::XMMatrixIdentity();
	std::vector<Snapshot> snapshots(10);
	snapshots.clear();
	Simulation simulation(graphicsRenderer, input, snapshots);
	return simulation.Run();
}

HRESULT InitWindow( HINSTANCE hInstance, int nCmdShow, HWND& windowHandle)
{
	WNDCLASSEX windowClass;
	windowClass.cbSize = sizeof( WNDCLASSEX );
	windowClass.style = CS_HREDRAW | CS_VREDRAW;
	windowClass.lpfnWndProc = &ClassicSimulationInput::WndProc;
	windowClass.cbClsExtra = 0;
	windowClass.cbWndExtra = 0;
	windowClass.hInstance = hInstance;
	windowClass.hIcon = LoadIcon( hInstance, ( LPCTSTR )IDI_PATHSIMULATION );
	windowClass.hCursor = LoadCursor( nullptr, IDC_ARROW );
	windowClass.hbrBackground = ( HBRUSH )( COLOR_WINDOW + 1 );
	windowClass.lpszMenuName = nullptr;
	windowClass.lpszClassName = L"PathSimulationWindowClass";
	windowClass.hIconSm = LoadIcon( windowClass.hInstance, ( LPCTSTR )IDI_PATHSIMULATION );
	if( !RegisterClassEx( &windowClass ) )
	{
		return E_FAIL;
	}
	RECT rc = { 0, 0, 1280, 720 };
	AdjustWindowRect( &rc, WS_OVERLAPPEDWINDOW, FALSE );
	windowHandle = CreateWindow( L"PathSimulationWindowClass",
		L"PathSimulation",
		WS_OVERLAPPEDWINDOW,
		CW_USEDEFAULT,
		CW_USEDEFAULT,
		rc.right - rc.left,
		rc.bottom - rc.top,
		nullptr,
		nullptr,
		hInstance,
		nullptr );
	if( !windowHandle )
	{
		return E_FAIL;
	}
	ShowWindow( windowHandle, nCmdShow );
	//SetCapture(windowHandle);
	return S_OK;
}

#undef IDI_PATHSIMULATION
#undef NUM_RANDOM_CUBES
#undef RANDOM_CUBE_SIZE
#undef RANDOM_CUBE_RANGE_LOWER
#undef RANDOM_CUBE_RANGE_UPPER